package org.mcflier.appuntamento.io.driver.rss.torrent;

import java.io.IOException;
import java.net.MalformedURLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.gnu.stealthp.rsslib.FeedVersion;
import org.gnu.stealthp.rsslib.RSSChannel;
import org.gnu.stealthp.rsslib.RSSException;
import org.gnu.stealthp.rsslib.RSSImage;
import org.gnu.stealthp.rsslib.RSSItem;
import org.gnu.stealthp.rsslib.RSSObject;
import org.gnu.stealthp.rsslib.RSSParser;
import org.gnu.stealthp.rsslib.RSSSequence;
import org.gnu.stealthp.rsslib.RSSSequenceElement;
import org.gnu.stealthp.rsslib.RSSSyndicationModule;
import org.gnu.stealthp.rsslib.RSSTextInput;
import org.mcflier.appuntamento.core.exception.PropertiesException;
import org.mcflier.appuntamento.data.model.TorrentList;
import org.mcflier.appuntamento.i18n.I18N;
import org.mcflier.appuntamento.i18n.exception.I18NLanguageNotFoundException;
import org.mcflier.appuntamento.io.driver.rss.RssTools;
import org.mcflier.appuntamento.io.exception.RSSConnectionException;
import org.mcflier.appuntamento.io.exception.RSSParsingException;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;

public class RssPirateBayDriver extends AbstractRssTorrentDriver {

	private TorrentList getList(String link, Timestamp since) throws RSSConnectionException, RSSParsingException{
		RssPirateBayHandler handler = new RssPirateBayHandler();
		try {
			RSSParser.parseXmlFile(RssTools.getRSSContentFile(link).getAbsolutePath(), handler, false);
			RSSChannel channel = handler.getRSSChannel();
			TorrentList torrentList = new TorrentList(link, channel.getTitle(), link);
			torrentList.setDescription(channel.getDescription());
			try {
				torrentList.setLanguage(I18N.getInstance().createLanguage(channel.getLanguage()));
			} catch (I18NLanguageNotFoundException e) {
				this.logger.warning("The torrent rss language was not retrieved: "+e.getMessage());
			} catch (PropertiesException e) {
				this.logger.warning("Problem while retrieving the torrent rss language: "+e.getMessage());
			}
			torrentList.setPubDate(RssTools.convertToTimestamp(channel.getPubDate()));
			torrentList.setLastBuildDate(RssTools.convertToTimestamp(channel.getLastBuildDate()));

			LinkedList objects = channel.getItems();
			Timestamp pubDate;
			RSSItem item;

			for (Object object : objects) {
				item = (RSSItem)object;
				pubDate = RssTools.convertToTimestamp(item.getPubDate());

				if(pubDate!=null && pubDate.after(since) || pubDate.equals(since)){
					TorrentList.TorrentListItem torrentItem = torrentList.new TorrentListItem(item.getTitle(), item.getComments());
					torrentItem.setPubDate(pubDate);
					torrentItem.setCategory(item.getUnknownField(RssPirateBayHandler.CATEGORY_TAG));
					torrentItem.setCreator(String.valueOf(item.getDoublinCoreElements().get(RssPirateBayHandler.DC_CREATOR_TAG)));
					torrentItem.setGuid(item.getUnknownField(RssPirateBayHandler.GUID_TAG));
					try {
						torrentItem.setContentLength(Long.valueOf(item.getUnknownField(RssPirateBayHandler.CONTENT_LENGTH_TAG)));
					} catch (NumberFormatException e) {
						this.logger.warning("Element: '"+torrentItem.getTitle()+"' has an invalid content length. Set it to -1");
						torrentItem.setContentLength(-1);
					}
					torrentItem.setInfoHash(item.getUnknownField(RssPirateBayHandler.INFO_HASH_TAG));
					torrentItem.setMagnetUri(item.getUnknownField(RssPirateBayHandler.MAGNET_URI_TAG));

					torrentList.addItem(torrentItem);
				}
			}

			return torrentList;
		} catch (MalformedURLException e) {
			throw new RSSConnectionException("Malformed rss url: "+link, e);
		}  catch (RSSException e) {
			throw new RSSParsingException("Error parsing the rss file: "+link, e);
		} catch (IOException e) {
			throw new RSSConnectionException("Error accessing url content: "+link, e);
		}
	}

	@Override
	public List<TorrentList> getHDTorrents() throws RSSConnectionException, RSSParsingException {
		this.logger.info("Getting HD rss list from Pirate Bay");
		return this.getHDTorrents(new Timestamp(0));
	}

	@Override
	public List<TorrentList> getHDTorrents(Timestamp since) throws RSSConnectionException, RSSParsingException {
		this.logger.info("Getting HD rss list from Pirate Bay since "+since);

		List<TorrentList> torrents = new ArrayList<TorrentList>();
		TorrentList list;
		List<String> hdResources = this.getHDResources();
		for (String resource : hdResources) {
			torrents.add(this.getList(resource, since));
		}

		return torrents;
	}

	@Override
	public List<TorrentList> getSDTorrents() throws RSSConnectionException, RSSParsingException {
		this.logger.info("Getting SD rss list from Pirate Bay");
		return this.getSDTorrents(new Timestamp(0));
	}

	@Override
	public List<TorrentList> getSDTorrents(Timestamp since) throws RSSConnectionException, RSSParsingException {
		this.logger.info("Getting SD rss list from Pirate Bay since "+since);

		List<TorrentList> torrents = new ArrayList<TorrentList>();
		TorrentList list;
		List<String> sdResources = this.getSDResources();
		for (String resource : sdResources) {
			torrents.add(this.getList(resource, since));
		}

		return torrents;
	}
	
	public class RssPirateBayHandler extends DefaultHandler{
		
		/**
		 * This class has been modified for the project Appuntamento,
		 * the original document was the class RSSHandler but now it is used
		 * for retrieving information from Pirate Bay
		 */

		private StringBuffer buff;
		private String current_tag;
		private RSSChannel chan;
		private RSSItem itm;
		private RSSImage img;
		private RSSSequence seq;
		private RSSSequenceElement seq_elem;
		private RSSTextInput input;
		private RSSSyndicationModule sy;
		private FeedVersion version;

		private boolean reading_chan;
		private boolean reading_item;
		private boolean reading_image;
		private boolean reading_seq;
		private boolean reading_input;
		private boolean have_dc;

		public static final String RDF_TAG			= "rdf:RDF";
		public static final String RSS_TAG			= "rss";
		public static final String CHANNEL_TAG		= "channel";
		public static final String TITLE_TAG		= "title";
		public static final String LINK_TAG			= "link";
		public static final String DESCRIPTION_TAG	= "description";
		public static final String ITEM_TAG			= "item";
		public static final String IMAGE_TAG		= "image";
		public static final String IMAGE_W_TAG		= "width";
		public static final String IMAGE_H_TAG		= "height";
		public static final String URL_TAG			= "url";
		public static final String SEQ_TAG			= "rdf:seq";
		public static final String SEQ_ELEMENT_TAG	= "rdf:li";
		public static final String TEXTINPUT_TAG	= "textinput";
		public static final String NAME_TAG			= "name";
		public static final String LANGUAGE_TAG		= "language";
		public static final String MANAGING_TAG		= "managingEditor";
		public static final String WMASTER_TAG		= "webMaster";
		public static final String COPY_TAG			= "copyright";
		public static final String PUB_DATE_TAG		= "pubDate";
		public static final String LAST_B_DATE_TAG	= "lastBuildDate";
		public static final String GENERATOR_TAG	= "generator";
		public static final String DOCS_TAG			= "docs";
		public static final String TTL_TAG			= "ttl";
		public static final String AUTHOR_TAG		= "author";
		public static final String COMMENTS_TAG		= "comments";
		public static final String CLOUD_TAG		= "cloud";     //TODO
		public static final String RATING_TAG		= "rating";    //TODO
		public static final String SKIPH_TAG		= "skipHours"; //TODO
		public static final String SKIPD_TAG		= "skipDays";  //TODO
		public static final String CATEGORY_TAG		= "category";  //TODO
		public static final String GUID_TAG			= "guid";
		public static final String CONTENT_LENGTH_TAG= "contentLength";
		public static final String INFO_HASH_TAG	= "infoHash";
		public static final String MAGNET_URI_TAG	= "magnetURI";


		public static final String DC_TITLE_TAG        = "dc:title";
		public static final String DC_CREATOR_TAG      = "dc:creator";
		public static final String DC_SUBJECT_TAG      = "dc:subject";
		public static final String DC_DESCRIPTION_TAG  = "dc:description";
		public static final String DC_PUBLISHER_TAG    = "dc:publisher";
		public static final String DC_CONTRIBUTOR_TAG  = "dc:contributor";
		public static final String DC_DATE_TAG         = "dc:date";
		public static final String DC_TYPE_TAG         = "dc:type";
		public static final String DC_FORMAT_TAG       = "dc:format";
		public static final String DC_IDENTIFIER_TAG   = "dc:identifier";
		public static final String DC_SOURCE_TAG       = "dc:source";
		public static final String DC_LANGUAGE_TAG     = "dc:language";
		public static final String DC_RELATION_TAG     = "dc:relation";
		public static final String DC_COVERAGE_TAG     = "dc:coverage";
		public static final String DC_RIGHTS_TAG       = "dc:rights";


		public static final String SY_PERIOD_TAG       = "sy:updatePeriod";
		public static final String SY_FREQ_TAG         = "sy:updateFrequency";
		public static final String SY_BASE_TAG         = "sy:updateBase";



		public RssPirateBayHandler(){
			buff          = new StringBuffer();
			current_tag   = null;
			chan          = new RSSChannel();
			reading_chan  = false;
			reading_item  = false;
			reading_image = false;
			reading_seq   = false;
			reading_input = false;
			have_dc       = false;
		}

		/**
		 * Receive notification of the start of an element.
		 * @param uri The Namespace URI, or the empty string if the element has no Namespace URI or if Namespace processing is not being performed.
		 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed
		 * @param qName The qualified name (with prefix), or the empty string if qualified names are not available
		 * @param attributes The attributes attached to the element. If there are no attributes, it shall be an empty Attributes object
		 */
		public void startElement(String uri,
				String localName,
				String qName,
				Attributes attributes){

			if (tagIsEqual(qName,RDF_TAG) || tagIsEqual(qName,RSS_TAG)){
				processDocumentVersion(attributes);
			}
			else if (tagIsEqual(qName,CHANNEL_TAG)){
				reading_chan = true;
				processChanAboutAttribute(attributes);
			}
			else if (tagIsEqual(qName,ITEM_TAG)){
				reading_item = true;
				reading_chan = false;
				itm = new RSSItem();
				processItemAboutAttribute(attributes);
			}
			else if (tagIsEqual(qName,IMAGE_TAG)){
				reading_image = true;
				reading_chan  = false;
				img = new RSSImage();
			}
			else if (tagIsEqual(qName,SEQ_TAG)){
				reading_seq = true;
				seq = new RSSSequence();
			}
			else if (tagIsEqual(qName,TEXTINPUT_TAG)){
				reading_input = true;
				reading_chan  = false;
				input = new RSSTextInput();
			}
			else if (tagIsEqual(qName,SEQ_ELEMENT_TAG))
				processSeqElement(attributes);
			else if (qName.toUpperCase().startsWith("SY:"))
				sy = new RSSSyndicationModule();

			current_tag = qName;

		}

		/**
		 * Receive notification of the end of an element
		 * @param uri The Namespace URI, or the empty string if the element has no Namespace URI or if Namespace processing is not being performed.
		 * @param localName The local name (without prefix), or the empty string if Namespace processing is not being performed
		 * @param qName The qualified name (with prefix), or the empty string if qualified names are not available
		 */
		public void endElement(String uri,
				String localName,
				String qName){


			String data = buff.toString().trim();

			if (qName.equals(current_tag)){
				data = buff.toString().trim();
				buff = new StringBuffer();
			}
			if (reading_chan)
				processChannel(qName,data);
			if (reading_item)
				processItem(qName,data);
			if (reading_image)
				processImage(qName,data);
			if (reading_input)
				processTextInput(qName,data);
			
			if (tagIsEqual(qName,CHANNEL_TAG)){
				reading_chan = false;
				chan.setSyndicationModule(sy);
			}
			else if (tagIsEqual(qName,ITEM_TAG)){
				reading_item = false;
				chan.addItem(itm);
			}
			else if (tagIsEqual(qName,IMAGE_TAG)){
				reading_image = false;
				chan.setRSSImage(img);
			}
			else if (tagIsEqual(qName,SEQ_TAG)){
				reading_seq = false;
				chan.addRSSSequence(seq);
			}
			else if (tagIsEqual(qName,TEXTINPUT_TAG)){
				reading_input = false;
				chan.setRSSTextInput(input);
			}
		}

		/**
		 * Receive notification of character data inside an element
		 * @param ch The characters.
		 * @param start The start position in the character array.
		 * @param length The number of characters to use from the character array.
		 */
		public void characters(char[] ch,
				int start,
				int length){


			String data = new String(ch,start,length);

			//Jump blank chunk
			if (data.trim().length() == 0)
				return;

			buff.append(data);

		}

		/**
		 * Try to get document version:
		 * Supported versions:0.91,0.92,1.0,2.0
		 * @param attr Attributes Version attributes
		 */
		private void processDocumentVersion(Attributes attr){

			version = new FeedVersion();

			String v_1 = attr.getValue("xmlns");
			String v_2 = attr.getValue("version");
			version.setNamespace(v_1);
			if (v_2 != null)
				version.setVersion(Double.parseDouble(v_2));
			else
				version.setVersion(0d);
		}

		/**
		 * Receive notification when parse are scannering an image
		 * @param qName The tag name
		 * @param data The tag Value
		 */
		private void processImage(String qName,String data){

			if (tagIsEqual(qName,TITLE_TAG))
				img.setTitle(data);
			else if (tagIsEqual(qName,LINK_TAG))
				img.setLink(data);
			else if (tagIsEqual(qName,URL_TAG))
				img.setUrl(data);
			else if (tagIsEqual(qName,IMAGE_W_TAG))
				img.setWidth(data);
			else if (tagIsEqual(qName,IMAGE_H_TAG))
				img.setHeight(data);
			else if (tagIsEqual(qName,DESCRIPTION_TAG))
				img.setDescription(data);
			else if (qName.toUpperCase().startsWith("DC:"))
				processDoublinCoreTags(qName,data,img);
		}


		/**
		 * Receive notification when parse are scannering a textinput
		 * @param qName The tag name
		 * @param data The tag Value
		 */

		private void processTextInput(String qName,String data){

			if (tagIsEqual(qName,TITLE_TAG))
				input.setTitle(data);
			else if (tagIsEqual(qName,LINK_TAG))
				input.setLink(data);
			else if (tagIsEqual(qName,NAME_TAG))
				input.setInputName(data);
			else if (tagIsEqual(qName,DESCRIPTION_TAG))
				input.setDescription(data);
			else if (qName.toUpperCase().startsWith("DC:"))
				processDoublinCoreTags(qName,data,input);

		}

		/**
		 * Receive notification when parse are scannering an Item
		 * @param qName The tag name
		 * @param data The tag Value
		 */
		private void processItem(String qName,String data){

			if (tagIsEqual(qName,TITLE_TAG))
				itm.setTitle(data);
			else if (tagIsEqual(qName,LINK_TAG))
				itm.setLink(data);
			else if (tagIsEqual(qName,DESCRIPTION_TAG))
				itm.setDescription(data);
			else if (tagIsEqual(qName,PUB_DATE_TAG))
				itm.setPubDate(data);
			else if (tagIsEqual(qName,PUB_DATE_TAG))
				itm.setPubDate(data);
			else if (tagIsEqual(qName,AUTHOR_TAG))
				itm.setAuthor(data);
			else if (tagIsEqual(qName,COMMENTS_TAG))
				itm.setComments(data);
			else if (qName.toUpperCase().startsWith("DC:"))
				processDoublinCoreTags(qName,data,itm);
			else if (tagIsEqual(qName, GUID_TAG))
				processUnknownField(qName, data, itm);
			else if(tagIsEqual(qName, CONTENT_LENGTH_TAG))
				processUnknownField(CONTENT_LENGTH_TAG, data, itm);
			else if(tagIsEqual(qName, INFO_HASH_TAG))
				processUnknownField(INFO_HASH_TAG, data, itm);
			else if(tagIsEqual(qName, MAGNET_URI_TAG))
				processUnknownField(MAGNET_URI_TAG, data, itm);
		}

		/**
		 * Receive notification when parse are scannering the Channel
		 * @param qName The tag name
		 * @param data The tag Value
		 */
		private void processChannel(String qName,String data){

			if (tagIsEqual(qName,TITLE_TAG))
				chan.setTitle(data);
			else if (tagIsEqual(qName,LINK_TAG))
				chan.setLink(data);
			else if (tagIsEqual(qName,DESCRIPTION_TAG))
				chan.setDescription(data);
			else if (tagIsEqual(qName,COPY_TAG))
				chan.setCopyright(data);
			else if (tagIsEqual(qName,PUB_DATE_TAG))
				chan.setPubDate(data);
			else if (tagIsEqual(qName,LAST_B_DATE_TAG))
				chan.setLastBuildDate(data);
			else if (tagIsEqual(qName,GENERATOR_TAG))
				chan.setGenerator(data);
			else if (tagIsEqual(qName,DOCS_TAG))
				chan.setDocs(data);
			else if (tagIsEqual(qName,TTL_TAG))
				chan.setTTL(data);
			else if (tagIsEqual(qName,LANGUAGE_TAG))
				chan.setLanguage(data);
			else if (qName.toUpperCase().startsWith("DC:"))
				processDoublinCoreTags(qName,data,chan);
			else if (qName.toUpperCase().startsWith("SY:"))
				processSyndicationTags(qName,data);
			else
				processUnknownField(qName, data, chan);

		}

		/**
		 * Receive notification when parse are scannering a doublin core element
		 * @param qName tag name
		 * @param data tag value
		 * @param o RSSObject
		 */
		private void processDoublinCoreTags(String qName, String data,RSSObject o){
			o.addDoublinCoreElement(qName.toLowerCase(),data);
		}
		
		private void processUnknownField(String qName, String data, RSSObject o){
			o.addUnknownField(qName, data);
		}

		private void processSyndicationTags(String qName, String data){
			if (tagIsEqual(qName,RssPirateBayHandler.SY_BASE_TAG))
				sy.setSyUpdateBase(data);
			else if (tagIsEqual(qName,RssPirateBayHandler.SY_FREQ_TAG))
				sy.setSyUpdateFrequency(data);
			else if (tagIsEqual(qName,RssPirateBayHandler.SY_PERIOD_TAG))
				sy.setSyUpdatePeriod(data);
		}

		/**
		 * Receive notification when parse are scannering a Sequence Item
		 * @param a The Atrribute of the tag
		 */
		private void processSeqElement(Attributes a){
			String res = a.getValue(0);
			seq_elem = new RSSSequenceElement();
			seq_elem.setResource(res);
			seq.addElement(seq_elem);
		}


		/**
		 * Receive notification when parse are scannering an Item attribute
		 * @param a the attribute
		 */
		private void processItemAboutAttribute(Attributes a){
			String res = a.getValue(0);
			itm.setAboutAttribute(res);
		}

		/**
		 * Receive notification when parse are scannering a Chan attribute
		 * @param a the attribute
		 */
		private void processChanAboutAttribute(Attributes a){
			String res = a.getValue(0);
			chan.setAboutAttribute(res);
		}

		/**
		 * Receive notification when parse are scannering an Inputtext attribute
		 * @param a the attribute
		 */
		private void processInputAboutAttribute(Attributes a){
			String res = a.getValue(0);
			input.setAboutAttribute(res);
		}

		/**
		 * Check against non-casesentive tag name
		 * @param a The first tag
		 * @param b The tag to check
		 * @return True if the tags are the same
		 */
		private boolean tagIsEqual(String a, String b){
			return a.equalsIgnoreCase(b);
		}

		/**
		 * Getting feed version
		 * Possible values are: 0.91,0.92,1.0,2.0,0.0(unknown)
		 * @return double Document version
		 */
		public  FeedVersion getDocumentVersion(){
			return version;
		}

		/**
		 * Get the RSSChannel Object back from the parser
		 * @return The RSSChannell Object
		 */
		public RSSChannel getRSSChannel(){
			return this.chan;
		}
	}
}
